#include <stdio.h>
#include <iostream>
#include "rep/rep.hpp"
#include "rep/running.hpp"

#include "vm/builtins.hpp"

using namespace rep;

namespace vm{

  Builtin* Builtin::builtins[MAX_BUILTINS];
  bool Builtin::initialized (false);
  bool BuiltinInstructions::initialized (false);

  void Builtin::initialize(){
    if(!initialized){
      BuiltinInstructions::init();
      builtins[INT_ADD] = new (GC) IntAddBuiltin();
      builtins[INT_NEGATE] = new (GC) IntNegateBuiltin();
      builtins[PRINT] = new (GC) PrintBuiltin();
      builtins[IF] = new (GC) IfBuiltin();
      initialized = true;
    }
  }

  Continuation IfBuiltin::execute(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    Slot condition;
    Slot then;
    Slot otherwise;
    Lambda* to_execute;
    stack = stack->pop(&condition)->pop(&then);
    if(pc.numpop == 3){
      stack = stack->pop(&otherwise);
    }
    if(condition.tag == Slot::VOID || 
       (condition.tag == Slot::BOOLEAN && condition.b == false)){
      //the if is false
      if(pc.numpop == 3){
        assert(otherwise.tag == Slot::LAMBDA);
        to_execute = otherwise.proc;
      }else{
        //no else branch, just return.
        standard_next.stack = stack;
        return standard_next;
      }
    }else{
      //the if is true
      assert(then.tag == Slot::LAMBDA);
      to_execute = then.proc;
    }
    //execute the function!

    //we need to be careful about whether we should pass in a differnt
    //ifdone continuation than return continuation.
    /*Slot* frame = FrameHelper::alloc(to_execute->scope());

    if(to_execute->scope()->requiresReturn()){
      //store the return continuation
      ContinuationHelper::assign(FrameHelper::immDirectLookup(frame, Slot::RETURN), 
                                 FrameHelper::ret(env));
    }
    if(to_execute->scope()->requiresIfDone()){
      //store the falloff continuation
      ContinuationHelper::assign(FrameHelper::immDirectLookup(frame, Slot::IFDONE), 
                                 new (GC) Continuation(standard_next));
    }
    stack = to_execute->scope()->packArgs(stack, frame, pc.numpop-1);

    return Continuation(stack, frame, to_execute->body(), 0);*/

    return to_execute->apply(stack, env, pc.numpop-1, 
                             FrameHelper::ret(env),
                             FrameHelper::except(env),
                             FrameHelper::yield(env),
                             new (GC) Continuation(standard_next),
                             FrameHelper::br(env),
                             FrameHelper::cont(env));
                             
  }

  Continuation IntAddBuiltin::execute(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    Slot a;
    Slot b;
    Slot result;
    stack = stack->pop(&a)->pop(&b);
    if(a.tag==Slot::INT){
      if(b.tag==Slot::INT){
        //XXX Deal w/ overflow later
        IntegerHelper::assign(&result, a.i + b.i);
        stack = stack->push(result);
        standard_next.stack = stack;
        return standard_next;
      }
    }

    return *FrameHelper::immException(env);
  }

  Continuation IntNegateBuiltin::execute(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    return *FrameHelper::immException(env);
  }

  Continuation IntMultiplyBuiltin::execute(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    return *FrameHelper::immException(env);
  }

  Continuation IntDivideBuiltin::execute(Cons* stack, Slot* env, Instruction pc, Continuation standard_next){
    return *FrameHelper::immException(env);
  }

  Continuation PrintBuiltin::execute(Cons* stack, Slot* env, Instruction pc, 
                                     Continuation standard_next){
    switch(stack->car.tag){
    case(Slot::INT):
      printf("%d", stack->car.i);
      break;
    case(Slot::STRING):
      cout<< *stack->car.str;
      break;
    default:
      cout<<"I don't know how to print that\n";
    }
    assert(standard_next.stack->cdr.tag == Slot::CONS);
    standard_next.stack = standard_next.stack->cdr.cell;
    return standard_next;
  }

  Instruction BuiltinInstructions::intAdd;
  Instruction BuiltinInstructions::print;
};


